package org.originit.factory.support;

import cn.hutool.core.convert.Convert;
import org.originit.exception.BeansException;
import org.originit.factory.config.BeanDefinition;
import org.originit.factory.config.BeanReference;
import org.originit.factory.config.PropertyValue;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {

    InstantiationStrategy strategy = new CglibInstantiationStrategy();

    @Override
    protected Object createBean(String name, BeanDefinition beanDefinition,Object[] args) throws BeansException {
        Object bean = createInstance(name,beanDefinition,args);
        applyPropertyValues(bean,beanDefinition);
        addSingleton(name,bean);
        return bean;
    }

    private void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws BeansException {
        Map<String, Field> map = Arrays.stream(beanDefinition.getType().getDeclaredFields()).collect(Collectors.<Field, String, Field>toMap(Field::getName, (field) -> field));
        for (PropertyValue propertyValue : beanDefinition.getPropertyValues()) {
            Field field = map.get(propertyValue.getName());
            if (field == null) {
                continue;
            }
            Object value = propertyValue.getValue();
            if (propertyValue.getValue() instanceof BeanReference) {
                value =  getBean(((BeanReference) propertyValue.getValue()).getName());
            }
            field.setAccessible(true);
            try {
                value = tryConvertType(field,value);
                field.set(bean,value);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                throw new BeansException(e);
            }
        }

    }

    private Object tryConvertType(Field field, Object value) {
        if (value instanceof String) {
            Class<?> type = field.getType();
            if (type == String.class) {
                return value;
            }
            return Convert.convert(type, value);
        }
        return value;
    }

    private Object createInstance(String name, BeanDefinition beanDefinition, Object[] args) throws BeansException {
        Constructor<?>[] declaredConstructors = beanDefinition.getType().getDeclaredConstructors();
        Constructor<?> constructor = null;
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
            if (parameterTypes.length != args.length) {
                continue;
            }
            if (args.length == 0) {
                constructor = declaredConstructor;
                break;
            }
            int i;
            for (i = 0; i < args.length; i++) {
                if (!args[i].getClass().equals(parameterTypes[i])) {
                    break;
                }
            }
            if (i == args.length) {
                constructor = declaredConstructor;
                break;
            }
        }
        if (constructor == null) {
            throw new BeansException("Constructor can not found with parameters types");
        }
        return strategy.instantiate(beanDefinition,name,constructor,args);
    }

}
